#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
CellCache is cache to store data into Cell. 

When cache is full, cache is dumped into store.
'''

__author__ = 'kuerant < kuerant@gmail.com >'
__version__ = '1.0.0'


import os, os.path
import copy

import error
import log

from key  import KeySmith
from help import insort,bisectLeft,bisectRight

from const import ROW_FOUND,ROW_NOT_FOUND,ROW_DELETED
from const import COLUMN_NOT_FOUND,COLUMN_FOUND,COLUMN_DELETED

#######################################################################

class   CellCache(object) :
    '''CellCache is cache to store data before writing into store files.
    '''
    def __init__(self) :
        self.clear()

        self._iter_row_keys = None
        self._iter_mode = False
        self._iter_current_row_key = None   # which row_key current iterated
        self._iter_current_cell_index = 0   # which column index of current row_key iterated
        ## __init__()

    ###################################################################

    def __iter__(self) :
        self._iter_row_keys = copy.copy(self._row_keys)
        self._iter_current_row_key_index = 0
        if self._iter_current_row_key_index < len(self._iter_row_keys) :
            self._iter_current_row_key = self._iter_row_keys[self._iter_current_row_key_index]
            self._iter_current_cell_index = 0

            self._iter_mode = True
        else :
            self._iter_mode = False
        return self
        ## __iter__()

    def next(self) :
        '''
        @return (key, data)
        '''
        if not self._iter_mode : raise StopIteration
        if self._iter_current_row_key_index >= len(self._iter_row_keys) : raise StopIteration

        row_key = self._iter_row_keys[self._iter_current_row_key_index]
        while True :
            while row_key not in self._content :
                self._iter_current_row_key_index += 1
                if self._iter_current_row_key_index >= len(self._iter_row_keys) : raise StopIteration
                row_key = self._iter_row_keys[self._iter_current_row_key_index]
                self._iter_current_cell_index = 0
            cells = self._content[row_key]

            if self._iter_current_cell_index >= len(cells) : 
                # iterate to the end of list
                self._iter_current_row_key_index += 1
                if self._iter_current_row_key_index >= len(self._iter_row_keys) : raise StopIteration
                row_key = self._iter_row_keys[self._iter_current_row_key_index]
                self._iter_current_cell_index = 0

                continue
            key, data = cells[ self._iter_current_cell_index ]
            self._iter_current_cell_index += 1
            return (key, data)
        ## next()

    ###################################################################

    def clear(self) :
        self._row_keys = []     # row-keys of data in cache
        self._content = {}      # data in cache

        self.size = 0           # size of data in cache
        ## clear()

    def insert(self, row_key, key, data) :
        if row_key in self._content :
            insort(self._content[row_key], (key,data))
        else :
            self._content[row_key] = [(key, data),]
        if row_key not in self._row_keys :
            insort(self._row_keys, row_key)

        self.size += len(key) + len(data)
        ## insert()

    def findColumn(self, row_key, column_key) :
        '''
        @return (status,data,timestamp)
        '''
        if row_key not in self._content : return (COLUMN_NOT_FOUND,None,None)

        key_values = self._content[row_key]
        key_range_from,key_range_to = KeySmith().getColumnKeyRange(column_key)
        # we sort record DESCENDLY, so we need SWAP range_from and range_to
        key_range_from,key_range_to = key_range_to,key_range_from

        #log.debug('findOne( row_key=%s )', `row_key`)
        index_from = bisectLeft(key_values, (key_range_from, None))
        #log.debug('FROM Key:%s Location:%d Set:%s', `key_range_from`, index_from, `key_values`)
        index_to = bisectRight(key_values, (key_range_to, None))
        #log.debug('  TO Key:%s Location:%d Set:%s', `key_range_to`, index_to, `key_values`)

        smith = KeySmith()
        if index_to > index_from and index_from < len(self._content) :
            for index in range(index_from, index_to) :
                key,data = key_values[index]
                smith.desemble(key)
                if smith.isDeleteColumnFlag() :
                    #log.error('TODO: findOne() how to handle DELETE_COLUMN flag (column-key:%s)', `column_key`)
                    #continue
                    return (COLUMN_DELETED, None, None)
                elif smith.isDeleteRowFlag() :
                    return (ROW_DELETED, None, None)
                else :
                    return (COLUMN_FOUND, data, smith.timestamp)

        return (COLUMN_NOT_FOUND,None,None)
        ## findColumn()

    def getColumnStatus(self, row_key, column_key) :
        '''
        @return (status,timestamp)
        '''
        if row_key not in self._content : return (COLUMN_NOT_FOUND,None)

        key_values = self._content[row_key]
        key_range_from,key_range_to = KeySmith().getColumnKeyRange(column_key)
        # we sort record DESCENDLY, so we need SWAP range_from and range_to
        key_range_from,key_range_to = key_range_to,key_range_from

        #log.debug('findOne( row_key=%s )', `row_key`)
        index_from = bisectLeft(key_values, (key_range_from, None))
        #log.debug('FROM Key:%s Location:%d Set:%s', `key_range_from`, index_from, `key_values`)
        index_to = bisectRight(key_values, (key_range_to, None))
        #log.debug('  TO Key:%s Location:%d Set:%s', `key_range_to`, index_to, `key_values`)

        smith = KeySmith()
        if index_to > index_from and index_from < len(self._content) :
            for index in range(index_from, index_to) :
                key,data = key_values[index]
                smith.desemble(key)
                if smith.isDeleteColumnFlag() :
                    #log.error('TODO: findOne() how to handle DELETE_COLUMN flag (column-key:%s)', `column_key`)
                    return (COLUMN_DELETED, smith.timestamp)
                elif smith.isDeleteRowFlag() :
                    return (ROW_DELETED, None)
                else :
                    return (COLUMN_FOUND, smith.timestamp)

        return (COLUMN_NOT_FOUND,None)
        ## getColumnStatus()

    ## class CellCache

